/*
 *  ======== plio.c ========
 */

#include <std.h>

#include <fxn.h>
#include <hwi.h>
#include <pip.h>
#include <sys.h>

#include <lio.h>
#include "plio.h"

#include <string.h>     /* for memset() */

static Void rxCallback(Arg arg, Uns nmaus);
static Void txCallback(Arg arg, Uns nmaus);
static Void transfer (PLIO_Handle plio, Uns numFrames);

#pragma CODE_SECTION(PLIO_new, ".text:init");
#pragma CODE_SECTION(PLIO_rxPrime, ".text:PLIO_rx");
#pragma CODE_SECTION(PLIO_rxStart, ".text:PLIO_rx");
#pragma CODE_SECTION(PLIO_txPrime, ".text:PLIO_tx");
#pragma CODE_SECTION(PLIO_txStart, ".text:PLIO_tx");
#pragma CODE_SECTION(txCallback, ".text:PLIO_tx");
#pragma CODE_SECTION(rxCallback, ".text:PLIO_rx");

/* 
 * These macros are used to work-around a problem with 'notifyWriter'
 * calls from PLIO_txStart() ...
 */
 #ifdef _55_
/* PIP_Obj structure is defined differently for 55x */
#define getNotifyWriterFxn(pip)          ((pip)->writerSock.notifyFxn.fxn)
#define setNotifyWriterFxn(pip, notify) \
         ((pip)->writerSock.notifyFxn.fxn = (notify))
#else
#define getNotifyWriterFxn(pip)         ((pip)->notifyWriter.fxn)
#define setNotifyWriterFxn(pip, notify) ((pip)->notifyWriter.fxn = (notify))
#endif

/*
 *  ======== PLIO_ATTRS ========
 */
PLIO_Attrs PLIO_ATTRS = {
    NULL,                       // name
    0                           // arg to LIO_open
};

/*
 *  ======== PLIO_new ========
 */
Void PLIO_new(
    PLIO_Handle plio,
    PIP_Handle pip,
    LIO_Mode mode,
    LIO_Fxns *fxns,
    PLIO_Attrs *attrs
)
{
    LIO_Tcallback cb;

    plio->pip = pip;
    plio->fxns = fxns;

    plio->submitCount = 0;
    plio->submitLimit = PIP_getWriterNumFrames(pip);

    if (attrs == NULL) {
        attrs = &PLIO_ATTRS;
    }

    if (mode == LIO_INPUT) {
        cb = rxCallback;
        plio->curdesc = PIP_getWriterCurdesc(pip);
    }
    else {
        cb = txCallback;
        plio->curdesc = PIP_getReaderCurdesc(pip);
    }

    /* open the channel */
    plio->controller = fxns->open(attrs->name, mode, attrs->openArgs,
        cb, (Arg)plio);
    if (plio->controller == NULL) {
        SYS_abort("PLIO_new: controller open failed");
    }
}

/*
 *  ======== transfer ========
 *  plio->submitCount
 *  The count of frames submitted to the LIO controller is stored
 *  in plio->submitCount. Transfer() looks ahead in the pipe using
 *  the pipe descriptor.  The number of frames taken from the pipe
 *  in this way is kept in plio->submitCount. Therefore the number of
 *  frames available in the pipe is the number of frames indicated by
 *  PIP_getReaderNumFrames() or PIP_getWriterNumFrames()
 *  less the value of plio->submitCount.
 *
 *  plio->submitLimit
 *  The capacity of the LIO controller is stored in plio->submitLimit.
 *  At initialization plio->submitLimit is set to a large number.
 *  When the LIO submit returns an error, plio->submitLimit is set to
 *  the value of plio->submitCount. Thereafter, plio->submitLimit
 *  represents the maximum frames the controller can accept.
 */
static Void transfer (PLIO_Handle plio, Uns numFrames)
{
    PIP_Curdesc *curdesc;                                               

    if (numFrames > plio->submitCount) {        // check for available frames
        curdesc = plio->curdesc;                // pipe descriptor
        plio->curdesc = curdesc->next;          // next pipe descriptor
        plio->submitCount++;                    // increment counter

        if ( 0 >                // submit returns negative # for error
            plio->fxns->submit (                // submit function
                plio->controller,               // channel object
                curdesc->addr,                  // frame address
                curdesc->size * sizeof(Int)     // require count in MAUs,
            )                                   // PIP element is a word
        )
        {                                       // frame not accepted
            plio->submitCount--;                // decrement counter
            plio->curdesc = curdesc;            // previous descriptor
            plio->submitLimit = plio->submitCount; // record max frames
        }
    }
}

/*
 *  ======== rxCallback ======== 
 */
static Void rxCallback (Arg arg, Uns nmaus)
{
    PLIO_Handle plio = (PLIO_Handle)(arg);
    PIP_Handle pip = plio->pip;
    /*
     * Must update the pipe before calling submit again.
     * We have used the pointer to the next frame,
     * but PIP_alloc has not been called yet.
     */
    PIP_alloc(pip);

    /* set size and return completed frame - PIP needs size in words! */
    PIP_setWriterSize(pip, nmaus / sizeof(Int));
    PIP_put(pip);

    /* decrement count of submitted frames */
    plio->submitCount--;

    /* start next transfer */
    transfer(plio, PIP_getWriterNumFrames(plio->pip));
}

/*
 *  ======== txCallback ======== 
 */
static Void txCallback (Arg arg, Uns nmaus)
{
    PLIO_Handle plio = (PLIO_Handle)(arg);
    PIP_Handle pip = plio->pip;
    /*
     * Must update the pipe before calling submit again.
     * We have used the pointer to the next frame,
     * but PIP_get has not been called yet.
     */
    PIP_get(pip);

    /* return completed frame */
    PIP_free(pip);

    /* decrement count of submitted frames */
    plio->submitCount--;

    /* start next transfer */
    transfer(plio, PIP_getReaderNumFrames(plio->pip));
}

/*
 *  ======== PLIO_rxPrime ======== 
 *  submit one frame if receive not busy
 */
Void PLIO_rxPrime (PLIO_Handle plio)
{
    Uns imask;

    imask = HWI_disable();
    if (plio->submitCount < plio->submitLimit) {
        transfer(plio, PIP_getWriterNumFrames(plio->pip));
    }
    HWI_restore(imask);
}

/*
 *  ======== PLIO_rxStart ======== 
 *  submit frames to receiver
 *
 *  CONSTRAINTS:
 *  -  PLIO_rxStart() may only be called in main() before interrupts
 *     are enabled
 */
Void PLIO_rxStart (PLIO_Handle plio, Uns frameCount)
{
    for ( ; frameCount > 0; frameCount--) {
        transfer(plio, PIP_getWriterNumFrames(plio->pip));
    }
}

/*
 *  ======== PLIO_txPrime ======== 
 *  submit one frame if transmit not busy
 */
Void PLIO_txPrime (PLIO_Handle plio)
{
    if (plio->submitCount < plio->submitLimit) {
        transfer(plio, PIP_getReaderNumFrames(plio->pip));
    }
}

/*
 *  ======== PLIO_txStart ======== 
 *  submit initialized frames to transmitter
 *
 *  CONSTRAINTS:
 *  -  PLIO_txStart() may only be called in main() before interrupts
 *     are enabled
 */
Void PLIO_txStart (PLIO_Handle plio, Uns frameCount, Uns initialValue)
{
    PIP_Handle  pip = plio->pip;
    char        *buf;
    Fxn         notifySave;

    /*
     * Work-around notifyWriter problem at startup.
     * Without this work-around, the PIP_alloc() calls below cause
     * notifyWriter to be called and the application to have incorrect
     * view of the PIP state (application thinks an empty output frame
     * is available when no frame is actually available).
     */
    notifySave = getNotifyWriterFxn(pip);
    setNotifyWriterFxn(pip, (Fxn)FXN_F_nop);

    for ( ; frameCount > 0; frameCount--) {
        if (PIP_getWriterNumFrames(pip) <= 0) break;
        PIP_alloc(pip);
        buf = PIP_getWriterAddr(pip);
        memset(buf, initialValue, PIP_getWriterSize(pip) * sizeof(Int));
        PIP_put(pip);
    }

    /* restore notifyWriter function to its' configured state */
    setNotifyWriterFxn(pip, notifySave);
}

